#include "LRUCache.h"
#include "Objects.h"

#include <cstdlib>
#include <cstring>
#include <cstdio>

LRUCache::LRUCache()
{
    //Do Nothing,  The data might be allocated by mymalloc not C++::new
}


LRUCache::~LRUCache()
{
    //Do Nothing,  The data might be allocated by mymalloc not C++::new
}


int LRUCache::Init(u64 max_mem_size)
{
 
    head = tail = NULL;
    mem_limit = max_mem_size;
    mem_used = 0;
    lock.Init();
}

void LRUCache::Destroy()
{
    //Lock is not necessary. Should guarantee thread-safe outside the function.
    CacheItem *cur;
    for(cur = head; cur != NULL; )
    {
        CacheItem *tmp = cur->next;
        myfree(cur);
        cur = tmp;
    }
    lock.Destroy();
}


u64 LRUCache::GetMemSize()
{
    return mem_used;
}


u32 LRUCache::GetItemCount()
{
    return item_count;
}

int LRUCache::GetItem(Item *item, char *buf)
{
    if (item->cache == NULL) return -1;
    else
    {
        lock.RLock();
        if (item->cache != NULL)
            memcpy(buf, item->cache->data, item->ValLength());
        else
        {
            lock.UnLock();
            return -1;
        }
        lock.UnLock();
        return 0;
    }
}


int LRUCache::UpdateItem(Item* item_new, Item* item_old, const char *buf)
{
    int new_size = item_new->ValLength();
    int old_size = item_old->ValLength();
    int res = -1;

    lock.WLock();
    if (item_old->cache == NULL || new_size > old_size || new_size + 16 < old_size)
    {
        //malloc new space
        CacheItem *cur;
        cur = (CacheItem*)mymalloc(new_size + sizeof(CacheItem));
        if (cur != NULL)
        {
            cur->prev = NULL;
            cur->next = head;
            head = cur;
            if (tail == NULL) tail = cur;
            memcpy(cur->data, buf, new_size);
            mem_used += new_size + sizeof(CacheItem);
            item_count++;
            item_new->cache = cur;
            cur->item = item_new;
            res = 0;
        }
        else
        {
            res = -1;
        }
        
        if (item_old->cache != NULL)
        {
            cur = item_old->cache;
            if (cur->next != NULL) cur->next->prev = cur->prev;
            if (cur->prev != NULL) cur->prev->next = cur->next;
            if (head == cur) head = cur->next;
            if (tail == cur) tail = cur->prev;
            item_old->cache = NULL;
            myfree(cur);
            mem_used -= old_size + sizeof(CacheItem);
            item_count--;
        }
    }
    else if (item_old->cache != NULL)
    {
        memcpy(item_old->cache->data, buf, new_size);
        mem_used = mem_used + new_size - old_size; //not precise, who cares!
        item_new->cache = item_old->cache;
        item_new->cache->item = item_new;
        item_old->cache = NULL;
        res = 0;
    }

    lock.UnLock();
    return res;
}

int LRUCache::DeleteItem(Item* item)
{
    CacheItem *cur;

    lock.WLock();
    if (item->cache == NULL) 
    {
        lock.UnLock();
        return -1;
    }
    cur = item->cache;
    if (cur->next != NULL) cur->next->prev = cur->prev;
    if (cur->prev != NULL) cur->prev->next = cur->next;
    if (head == cur) head = cur->next;
    if (tail == cur) tail = cur->prev;
    mem_used -= sizeof(CacheItem) + item->ValLength();
    item_count--;
    item->cache = NULL;
    myfree(cur);
    lock.UnLock();
    return 0;
}

int LRUCache::SetItem(Item *item, const char *buf)
{
    CacheItem *cur;

    lock.WLock();
    if (mem_limit < 1 * KB) 
    {
        item->cache = NULL;
        lock.UnLock();
        return -1;
    }

    if (mem_used + item->ValLength() + sizeof(CacheItem) > mem_limit)
    {
        release_space(64 * KB);
    }
    cur = (CacheItem*)mymalloc(sizeof(CacheItem) + item->ValLength());
    if (cur == NULL) 
    {
        item->cache = NULL;
        lock.UnLock();
        return -1;
    }
    memcpy(cur->data, buf, item->ValLength());
    cur->prev = NULL;
    cur->next = head;
    if (head != NULL) head->prev = cur;
    head = cur;
    if (tail == NULL) tail = cur;
    mem_used += item->ValLength() + sizeof(CacheItem);
    item_count++;
    item->cache = cur;
    cur->item = item;
    lock.UnLock();
    return 0;
}


void LRUCache::Resize(u64 new_size)
{
    mem_limit = new_size;
    
    if (mem_used > new_size)
    {
        lock.WLock();
        release_space(mem_used - new_size);
        lock.UnLock();
    }
}


void LRUCache::release_space(u64 size)
{
    u64 size_freed = 0;
    while(size_freed < size)
    {
        CacheItem *cur = tail;
        if (cur == NULL) break;
        tail = tail->prev;
        if (tail != NULL) 
            tail->next = NULL;
        cur->item->cache = NULL;
        item_count--;
        size_freed += cur->item->ValLength() + sizeof(CacheItem);
        myfree(cur);
    }
    mem_used -= size_freed;
}
